//
//Copyright 2001-2004 Stottler Henke Associates, Inc.
//All Rights Reserved.
////////////////////////////////////////////////////////////////////////////////

package com.stottlerhenke.simbionic.engine.core;

import com.stottlerhenke.simbionic.api.*;
import com.stottlerhenke.simbionic.common.parser.*;
import com.stottlerhenke.simbionic.common.*;
import com.stottlerhenke.simbionic.engine.file.SB_StreamReader;
import com.stottlerhenke.simbionic.engine.parser.*;
import com.stottlerhenke.simbionic.engine.SB_SingletonBook;

/**
 * Represents a single variable binding, complete with the capability
 * to evaluate itself.
 */
public class SB_VarBinding  
{
  protected String		_varName;
  protected String		_expressStr;
  protected SB_Expression	_expressTree;
  protected EBindType		_bindType;

  // cached information for efficiency
  protected boolean		_bVarBinding;
  protected boolean		_bOutBinding;
  protected boolean		_bInBinding;

  public SB_VarBinding(int kind) 
  {
    _expressTree = null;
		_bindType = EBindType.getType(kind);
    _bVarBinding = false;
    _bOutBinding = false;
    _bInBinding = false;
  }
// Accessors

  /**
   * @return true if this binding binds a variable, false if it binds a parameter
   */
  public boolean IsVarBinding()  { return _bVarBinding; }

  /**
	 * @return true if this binding both a) binds an out or in/out parameter
	 *			and b) binds it to a single variable, false otherwise
	 */
	public boolean IsOutBinding()  { return _bOutBinding; }

  /**
   * @return true if this binding binds an in or in/out parameter, false otherwise
   */
  public boolean IsInBinding()  { return _bInBinding; }

  /**
   * @return the type of the variable being bound
   */
  public EBindType GetBindingType()  { return _bindType; }

  /**
   * @return the name of the bound (left-hand) side variable
   */
  public String GetBoundName()  { return _varName; }

  /**
   * Loads a single variable binding from a specification.
   * @param reader the stream reader from which to read it
   */
  public void load(SB_StreamReader reader,SB_SingletonBook book)
		throws SB_FileException
	{
		// get name of bound variable
		_varName = reader.readPString();

		// get right-hand expression
		_expressStr = reader.readPString();
	}

	public void initialize(SB_SingletonBook book)
	{
	  if(SIM_Constants.DEBUG_INFO_ON)
	    book.getLogger().log(".Initializing binding " + _varName + " <- " + _expressStr,SB_Logger.INIT);

	  _expressTree = (SB_Expression) book.getParser().Parse(_expressStr);

		// cache for runtime efficiency
		if ((_bindType == EBindType.kBehaviorVar) || (_bindType == EBindType.kGlobalVar))
			_bVarBinding = true;
	}
	
	/**
	 * Replaces all instances of SB_Function in the expression tree that
	 * match the name of newMethod.
	 * @param newMethod the method with which to replace existing methods
	 */
	public void replaceFunction(SB_Function newMethod)
	{	
	    _expressTree.replaceFunction(newMethod);
	}	

	public void InitParamBinding(EParamDir dir){
	  // cache stuff for efficiency at runtime
	  if ((dir == EParamDir.kOut) || (dir == EParamDir.kInOut)){
	    // it's effectively only an "out" binding if the RHS is a valid l-value
	    if (_expressTree.GetType() == EExpressionType.kVariable)
	      _bOutBinding = true;
	  }

	  if ((dir == EParamDir.kIn) || (dir == EParamDir.kInOut))
	    _bInBinding = true;
	}

	public void Apply(SB_Entity p,SB_ExecutionFrame contextFrame,
	                                                   SB_ExecutionFrame newFrame,
	                                                   SB_Logger logger) throws SB_Exception
	{
		try
		{
		    // evaluate the right-hand expression in the current execution frame
		    SB_Variable value = _expressTree.Evaluate(p,contextFrame);
		    
		    if(value == null)
		    	return;
		
		    if(SIM_Constants.DEBUG_INFO_ON)
		      logger.log("[" + p.toString() + ",STK " + contextFrame.GetStackDepth()
		      			+ "] BINDING:\tbinding behavior param " + _varName + " <- " + _expressStr
		      			+ " = " + value.toString(),SB_Logger.BINDING);
		
		    //Create a new SB_Varaible of the same type as the existing and set with the correct SB_Param
		    SB_Variable currentValue = newFrame.GetVariable(_varName);
		    SB_Variable newValue = currentValue.Clone();
		    newValue.setSB_Param(value.getSB_Param());
		    
		    // assign it to the behavior parameter in the *new* execution frame
		    newFrame.SetVariable(_varName, newValue);
		}
		catch(SB_Exception e)
		{
			throw new SB_Exception("Failed to apply variable binding '" + _expressStr + "' to variable '" + _varName + "'.",e);
		}
	}

	public SB_Variable ApplyTo(SB_Entity p,SB_ExecutionFrame contextFrame, SB_Logger logger)
    throws SB_Exception
	{
		try
		{
		    // evaluate the right-hand expression in the current execution frame
		    SB_Variable param = _expressTree.Evaluate(p,contextFrame);
		    
		    if(param == null)
		    	return param;
		
		    if(SIM_Constants.DEBUG_INFO_ON)
						logger.log("[" + p.toString() + ",STK " + contextFrame.GetStackDepth()
		  	  	  + "] BINDING:\tbinding action param " + _varName + " <- "
		    	  	+ _expressStr + " = " + param.toString() + " (" + param.GetType().getState() + ")",SB_Logger.BINDING);
		
		    return param;
		}
		catch(SB_Exception e)
		{
			throw new SB_Exception("Failed to apply variable binding '" + _expressStr + "'.",e);
		}
	}

	public void Apply(SB_Entity p,SB_ExecutionFrame contextFrame, SB_Logger logger)
		throws SB_Exception
	{
		try
		{	
			// evaluate the right-hand expression in the current execution frame
			SB_Variable value = _expressTree.Evaluate(p,contextFrame);
			
			if( value == null )
				return;
			
			if(SIM_Constants.DEBUG_INFO_ON)
				logger.log("[" + p.toString() + ",STK " + contextFrame.GetStackDepth()
						+ "] BINDING:\t binding behavior var " + _varName + " <- " +
						_expressStr + " = " + value.toString(),SB_Logger.BINDING);
			
			//  Create a new SB_Varaible of the same type as the existing and set with the correct SB_Param
			SB_Variable currentValue = contextFrame.GetVariable(_varName);
			SB_Variable newValue = currentValue.Clone();
			newValue.setSB_Param(value.getSB_Param());
			
			// assign it to the behavior variable
			contextFrame.SetVariable(_varName,newValue);
		}
		catch(SB_Exception e)
		{
			throw new SB_Exception("Failed to apply variable binding '" + _expressStr + "' to variable '" + _varName + "'.",e);
		}
	}

	 public void ReverseApply(SB_Entity p,SB_ExecutionFrame contextFrame,
	                   SB_ExecutionFrame sourceFrame, SB_Logger logger) throws SB_Exception
	 {
	   // for behaviors, we sync parameters from a behavior in one frame with variables
	   // from a behavior in another frame
			 if(SIM_Constants.DEBUG_INFO_ON)
	       logger.log("[" + p.toString() + ",STK " + contextFrame.GetStackDepth()
	      				+ "] BINDING:\tsyncing behavior param " + _varName + "with var "
	      				+ _expressStr + " = " + sourceFrame.GetVariable(_varName).toString(),SB_Logger.BINDING);

	     contextFrame.SetVariable(_expressStr, sourceFrame.GetVariable(_varName));
	 }

	 public void ReverseApply(SB_Entity p,SB_ExecutionFrame contextFrame,
	                          SB_Variable param, SB_Logger logger)
	 {
	    if(SIM_Constants.DEBUG_INFO_ON)
	      logger.log("[" + p.toString() + ",STK " + contextFrame.GetStackDepth()
	      		+ "] BINDING\tsyncing action param " + _varName + " with var "
	      		+ _expressStr + " = " + param.toString(),SB_Logger.BINDING);

	    contextFrame.SetVariable(_expressStr, param);
	 }

}